#include <RendererDX11/RendererDX11PCH.h>

#include <Foundation/Containers/IterateBits.h>
#include <RendererDX11/CommandEncoder/CommandEncoderImplDX11.h>
#include <RendererDX11/Device/DeviceDX11.h>
#include <RendererDX11/Pools/FencePoolDX11.h>
#include <RendererDX11/Pools/QueryPoolDX11.h>
#include <RendererDX11/Resources/BufferDX11.h>
#include <RendererDX11/Resources/ReadbackBufferDX11.h>
#include <RendererDX11/Resources/ReadbackTextureDX11.h>
#include <RendererDX11/Resources/RenderTargetViewDX11.h>
#include <RendererDX11/Resources/TextureDX11.h>
#include <RendererDX11/Shader/BindGroupLayoutDX11.h>
#include <RendererDX11/Shader/ShaderDX11.h>
#include <RendererDX11/Shader/VertexDeclarationDX11.h>
#include <RendererDX11/State/ComputePipelineDX11.h>
#include <RendererDX11/State/GraphicsPipelineDX11.h>
#include <RendererDX11/State/StateDX11.h>
#include <RendererFoundation/CommandEncoder/CommandEncoder.h>

#include <d3d11_1.h>

ezGALCommandEncoderImplDX11::ezGALCommandEncoderImplDX11(ezGALDeviceDX11& ref_deviceDX11)
  : m_GALDeviceDX11(ref_deviceDX11)
{
  m_pDXContext = m_GALDeviceDX11.GetDXImmediateContext();

  if (FAILED(m_pDXContext->QueryInterface(__uuidof(ID3DUserDefinedAnnotation), (void**)&m_pDXAnnotation)))
  {
    ezLog::Warning("Failed to get annotation interface. GALContext marker will not work");
  }
}

ezGALCommandEncoderImplDX11::~ezGALCommandEncoderImplDX11()
{
  EZ_GAL_DX11_RELEASE(m_pDXAnnotation);
}


void ezGALCommandEncoderImplDX11::EndFrame()
{
  m_AlreadyUpdatedTransientBuffers.Clear();
}

// State setting functions

void ezGALCommandEncoderImplDX11::SetShader(const ezGALShader* pShader)
{
  m_uiTessellationPatchControlPoints = 0;
  ID3D11VertexShader* pVS = nullptr;
  ID3D11HullShader* pHS = nullptr;
  ID3D11DomainShader* pDS = nullptr;
  ID3D11GeometryShader* pGS = nullptr;
  ID3D11PixelShader* pPS = nullptr;
  ID3D11ComputeShader* pCS = nullptr;

  if (pShader != nullptr)
  {
    const ezGALShaderDX11* pDXShader = static_cast<const ezGALShaderDX11*>(pShader);

    pVS = pDXShader->GetDXVertexShader();
    pHS = pDXShader->GetDXHullShader();
    pDS = pDXShader->GetDXDomainShader();
    pGS = pDXShader->GetDXGeometryShader();
    pPS = pDXShader->GetDXPixelShader();
    pCS = pDXShader->GetDXComputeShader();
  }

  if (pVS != m_pBoundShaders[ezGALShaderStage::VertexShader])
  {
    m_pDXContext->VSSetShader(pVS, nullptr, 0);
    m_pBoundShaders[ezGALShaderStage::VertexShader] = pVS;
  }

  if (pHS != m_pBoundShaders[ezGALShaderStage::HullShader])
  {
    m_pDXContext->HSSetShader(pHS, nullptr, 0);
    m_pBoundShaders[ezGALShaderStage::HullShader] = pHS;
    if (pHS)
    {
      m_uiTessellationPatchControlPoints = pShader->GetDescription().m_ByteCodes[ezGALShaderStage::HullShader]->m_uiTessellationPatchControlPoints;
    }
    else
    {
      m_uiTessellationPatchControlPoints = 0;
    }
  }

  if (pDS != m_pBoundShaders[ezGALShaderStage::DomainShader])
  {
    m_pDXContext->DSSetShader(pDS, nullptr, 0);
    m_pBoundShaders[ezGALShaderStage::DomainShader] = pDS;
  }

  if (pGS != m_pBoundShaders[ezGALShaderStage::GeometryShader])
  {
    m_pDXContext->GSSetShader(pGS, nullptr, 0);
    m_pBoundShaders[ezGALShaderStage::GeometryShader] = pGS;
  }

  if (pPS != m_pBoundShaders[ezGALShaderStage::PixelShader])
  {
    m_pDXContext->PSSetShader(pPS, nullptr, 0);
    m_pBoundShaders[ezGALShaderStage::PixelShader] = pPS;
  }

  if (pCS != m_pBoundShaders[ezGALShaderStage::ComputeShader])
  {
    m_pDXContext->CSSetShader(pCS, nullptr, 0);
    m_pBoundShaders[ezGALShaderStage::ComputeShader] = pCS;
  }
}

void ezGALCommandEncoderImplDX11::SetBindGroupPlatform(ezUInt32 uiBindGroup, const ezGALBindGroupCreationDescription& bindGroup)
{
  EZ_IGNORE_UNUSED(uiBindGroup);
  const ezGALBindGroupLayoutDX11* pLayout = static_cast<const ezGALBindGroupLayoutDX11*>(m_GALDeviceDX11.GetBindGroupLayout(bindGroup.m_hBindGroupLayout));
  ezArrayPtr<const ezShaderResourceBinding> bindings = pLayout->GetDescription().m_ResourceBindings;
  const ezUInt32 uiBindings = bindings.GetCount();
  for (ezUInt32 i = 0; i < uiBindings; ++i)
  {
    const ezShaderResourceBinding& binding = bindings[i];
    const ezGALBindGroupItem& item = bindGroup.m_BindGroupItems[i];

    switch (binding.m_ResourceType)
    {
      case ezGALShaderResourceType::ConstantBuffer:
      {
        const ezGALBufferDX11* pBuffer = static_cast<const ezGALBufferDX11*>(m_GALDeviceDX11.GetBuffer(item.m_Buffer.m_hBuffer));
        if (item.m_Flags.IsSet(ezGALBindGroupItemFlags::EmptyBinding))
          pBuffer = nullptr;

        SetConstantBuffer(binding, pBuffer);
      }
      break;
      case ezGALShaderResourceType::Texture:
      {
        const ezGALTextureDX11* pTexture = static_cast<const ezGALTextureDX11*>(m_GALDeviceDX11.GetTexture(item.m_Texture.m_hTexture));
        if (item.m_Flags.IsSet(ezGALBindGroupItemFlags::EmptyBinding))
          pTexture = nullptr;

        if (pTexture != nullptr && UnsetUnorderedAccessViews(pTexture))
        {
          FlushDeferredStateChanges().IgnoreResult();
        }

        ID3D11ShaderResourceView* pResourceViewDX11 = pTexture != nullptr ? pTexture->GetSRV(item.m_Texture.m_TextureRange, item.m_Texture.m_OverrideViewFormat) : nullptr;

        SetResourceView(binding, pTexture, pResourceViewDX11);
      }
      break;
      case ezGALShaderResourceType::TextureRW:
      {
        const ezGALTextureDX11* pTexture = static_cast<const ezGALTextureDX11*>(m_GALDeviceDX11.GetTexture(item.m_Texture.m_hTexture));
        if (item.m_Flags.IsSet(ezGALBindGroupItemFlags::EmptyBinding))
          pTexture = nullptr;

        if (pTexture != nullptr && UnsetResourceViews(pTexture))
        {
          FlushDeferredStateChanges().IgnoreResult();
        }

        ID3D11UnorderedAccessView* pUnorderedAccessViewDX11 = pTexture ? pTexture->GetUAV(item.m_Texture.m_TextureRange, item.m_Texture.m_OverrideViewFormat) : nullptr;
        SetUnorderedAccessView(binding, pUnorderedAccessViewDX11, pTexture);
      }
      break;

      case ezGALShaderResourceType::TexelBuffer:
      case ezGALShaderResourceType::StructuredBuffer:
      case ezGALShaderResourceType::ByteAddressBuffer:
      {
        const ezGALBufferDX11* pBuffer = static_cast<const ezGALBufferDX11*>(m_GALDeviceDX11.GetBuffer(item.m_Buffer.m_hBuffer));
        if (item.m_Flags.IsSet(ezGALBindGroupItemFlags::EmptyBinding))
          pBuffer = nullptr;

        if (pBuffer != nullptr && UnsetUnorderedAccessViews(pBuffer))
        {
          FlushDeferredStateChanges().IgnoreResult();
        }

        ID3D11ShaderResourceView* pResourceViewDX11 = pBuffer != nullptr ? pBuffer->GetSRV(item.m_Buffer.m_BufferRange, binding.m_ResourceType, item.m_Buffer.m_OverrideTexelBufferFormat) : nullptr;

        SetResourceView(binding, pBuffer, pResourceViewDX11);
      }
      break;

      case ezGALShaderResourceType::TexelBufferRW:
      case ezGALShaderResourceType::StructuredBufferRW:
      case ezGALShaderResourceType::ByteAddressBufferRW:
      {
        const ezGALBufferDX11* pBuffer = static_cast<const ezGALBufferDX11*>(m_GALDeviceDX11.GetBuffer(item.m_Buffer.m_hBuffer));
        if (item.m_Flags.IsSet(ezGALBindGroupItemFlags::EmptyBinding))
          pBuffer = nullptr;

        if (pBuffer != nullptr && UnsetResourceViews(pBuffer))
        {
          FlushDeferredStateChanges().IgnoreResult();
        }

        ID3D11UnorderedAccessView* pUnorderedAccessViewDX11 = pBuffer != nullptr ? pBuffer->GetUAV(item.m_Buffer.m_BufferRange, binding.m_ResourceType, item.m_Buffer.m_OverrideTexelBufferFormat) : nullptr;
        SetUnorderedAccessView(binding, pUnorderedAccessViewDX11, pBuffer);
      }
      break;
      case ezGALShaderResourceType::Sampler:
      {
        const ezGALSamplerStateDX11* pSampler = static_cast<const ezGALSamplerStateDX11*>(m_GALDeviceDX11.GetSamplerState(item.m_Sampler.m_hSampler));
        SetSamplerState(binding, pSampler);
      }
      break;
      case ezGALShaderResourceType::TextureAndSampler:
      default:
        break;
    }
  }
}

void ezGALCommandEncoderImplDX11::SetBindGroupPlatform(ezUInt32 uiBindGroup, const ezGALBindGroup* pBindGroup)
{
  // There is no way to persist bind groups in DX11, so this just redirects to the transient bind group code path.
  SetBindGroupPlatform(uiBindGroup, pBindGroup->GetDescription());
}

void ezGALCommandEncoderImplDX11::SetConstantBuffer(const ezShaderResourceBinding& binding, const ezGALBuffer* pBuffer)
{
  EZ_ASSERT_RELEASE(binding.m_iSlot < EZ_GAL_MAX_CONSTANT_BUFFER_COUNT, "Constant buffer slot index too big!");

  ID3D11Buffer* pBufferDX11 = pBuffer != nullptr ? static_cast<const ezGALBufferDX11*>(pBuffer)->GetDXBuffer() : nullptr;
  if (m_pBoundConstantBuffers[binding.m_iSlot] == pBufferDX11)
    return;

  m_pBoundConstantBuffers[binding.m_iSlot] = pBufferDX11;
  // The GAL doesn't care about stages for constant buffer, but we need to handle this internally.
  for (ezUInt32 stage = 0; stage < ezGALShaderStage::ENUM_COUNT; ++stage)
    m_BoundConstantBuffersRange[stage].SetToIncludeValue(binding.m_iSlot);
}

void ezGALCommandEncoderImplDX11::SetSamplerState(const ezShaderResourceBinding& binding, const ezGALSamplerState* pSamplerState)
{
  EZ_ASSERT_RELEASE(binding.m_iSlot < EZ_GAL_MAX_SAMPLER_COUNT, "Sampler state slot index too big!");

  ID3D11SamplerState* pSamplerStateDX11 = pSamplerState != nullptr ? static_cast<const ezGALSamplerStateDX11*>(pSamplerState)->GetDXSamplerState() : nullptr;

  for (ezGALShaderStage::Enum stage : ezIterateBitIndices<ezUInt16, ezGALShaderStage::Enum>(binding.m_Stages.GetValue()))
  {
    if (m_pBoundSamplerStates[stage][binding.m_iSlot] != pSamplerStateDX11)
    {
      m_pBoundSamplerStates[stage][binding.m_iSlot] = pSamplerStateDX11;
      m_BoundSamplerStatesRange[stage].SetToIncludeValue(binding.m_iSlot);
    }
  }
}


void ezGALCommandEncoderImplDX11::SetResourceView(const ezShaderResourceBinding& binding, const ezGALResourceBase* pResource, ID3D11ShaderResourceView* pResourceViewDX11)
{
  for (ezGALShaderStage::Enum stage : ezIterateBitIndices<ezUInt16, ezGALShaderStage::Enum>(binding.m_Stages.GetValue()))
  {
    auto& boundShaderResourceViews = m_pBoundShaderResourceViews[stage];
    boundShaderResourceViews.EnsureCount(binding.m_iSlot + 1);
    auto& resourcesForResourceViews = m_ResourcesForResourceViews[stage];
    resourcesForResourceViews.EnsureCount(binding.m_iSlot + 1);
    if (boundShaderResourceViews[binding.m_iSlot] != pResourceViewDX11)
    {
      boundShaderResourceViews[binding.m_iSlot] = pResourceViewDX11;
      resourcesForResourceViews[binding.m_iSlot] = pResource;
      m_BoundShaderResourceViewsRange[stage].SetToIncludeValue(binding.m_iSlot);
    }
  }
}

void ezGALCommandEncoderImplDX11::SetUnorderedAccessView(const ezShaderResourceBinding& binding, ID3D11UnorderedAccessView* pUnorderedAccessViewDX11, const ezGALResourceBase* pResource)
{
  m_BoundUnorderedAccessViews.EnsureCount(binding.m_iSlot + 1);
  m_ResourcesForUnorderedAccessViews.EnsureCount(binding.m_iSlot + 1);
  if (m_BoundUnorderedAccessViews[binding.m_iSlot] != pUnorderedAccessViewDX11)
  {
    m_BoundUnorderedAccessViews[binding.m_iSlot] = pUnorderedAccessViewDX11;
    m_ResourcesForUnorderedAccessViews[binding.m_iSlot] = pResource;
    m_BoundUnorderedAccessViewsRange.SetToIncludeValue(binding.m_iSlot);
  }
}

void ezGALCommandEncoderImplDX11::SetPushConstantsPlatform(ezArrayPtr<const ezUInt8> data)
{
  EZ_IGNORE_UNUSED(data);
  EZ_REPORT_FAILURE("DX11 does not support push constants, this function should not have been called.");
}

// Query functions

ezGALTimestampHandle ezGALCommandEncoderImplDX11::InsertTimestampPlatform()
{
  return m_GALDeviceDX11.GetQueryPool().InsertTimestamp();
}

ezGALOcclusionHandle ezGALCommandEncoderImplDX11::BeginOcclusionQueryPlatform(ezEnum<ezGALQueryType> type)
{
  return m_GALDeviceDX11.GetQueryPool().BeginOcclusionQuery(type);
}

void ezGALCommandEncoderImplDX11::EndOcclusionQueryPlatform(ezGALOcclusionHandle hOcclusion)
{
  m_GALDeviceDX11.GetQueryPool().EndOcclusionQuery(hOcclusion);
}

ezGALFenceHandle ezGALCommandEncoderImplDX11::InsertFencePlatform()
{
  return m_GALDeviceDX11.GetFenceQueue().GetCurrentFenceHandle();
}

// Resource update functions

void ezGALCommandEncoderImplDX11::CopyBufferPlatform(const ezGALBuffer* pDestination, const ezGALBuffer* pSource)
{
  ID3D11Buffer* pDXDestination = static_cast<const ezGALBufferDX11*>(pDestination)->GetDXBuffer();
  ID3D11Buffer* pDXSource = static_cast<const ezGALBufferDX11*>(pSource)->GetDXBuffer();

  m_pDXContext->CopyResource(pDXDestination, pDXSource);
}

void ezGALCommandEncoderImplDX11::CopyBufferRegionPlatform(const ezGALBuffer* pDestination, ezUInt32 uiDestOffset, const ezGALBuffer* pSource, ezUInt32 uiSourceOffset, ezUInt32 uiByteCount)
{
  ID3D11Buffer* pDXDestination = static_cast<const ezGALBufferDX11*>(pDestination)->GetDXBuffer();
  ID3D11Buffer* pDXSource = static_cast<const ezGALBufferDX11*>(pSource)->GetDXBuffer();

  D3D11_BOX srcBox = {uiSourceOffset, 0, 0, uiSourceOffset + uiByteCount, 1, 1};
  m_pDXContext->CopySubresourceRegion(pDXDestination, 0, uiDestOffset, 0, 0, pDXSource, 0, &srcBox);
}

void ezGALCommandEncoderImplDX11::UpdateBufferPlatform(const ezGALBuffer* pDestination, ezUInt32 uiDestOffset, ezArrayPtr<const ezUInt8> sourceData, ezGALUpdateMode::Enum updateMode)
{
  EZ_CHECK_ALIGNMENT(sourceData.GetPtr(), 16);

  ID3D11Buffer* pDXDestination = static_cast<const ezGALBufferDX11*>(pDestination)->GetDXBuffer();

  // On DX11 we can treat non-transient and transient constant buffers equally.
  if (updateMode == ezGALUpdateMode::TransientConstantBuffer || pDestination->GetDescription().m_BufferFlags.IsSet(ezGALBufferUsageFlags::ConstantBuffer))
  {
    EZ_ASSERT_DEV(uiDestOffset == 0 && sourceData.GetCount() == pDestination->GetSize(),
      "Constant buffers can't be updated partially (and we don't check for DX11.1)!");

    D3D11_MAPPED_SUBRESOURCE MapResult;
    if (SUCCEEDED(m_pDXContext->Map(pDXDestination, 0, D3D11_MAP_WRITE_DISCARD, 0, &MapResult)))
    {
      memcpy(MapResult.pData, sourceData.GetPtr(), sourceData.GetCount());

      m_pDXContext->Unmap(pDXDestination, 0);
    }
  }
  else
  {
    const bool bTransient = pDestination->GetDescription().m_BufferFlags.IsSet(ezGALBufferUsageFlags::Transient);
    if (!bTransient)
    {
      if (ezGALDeviceDX11::TempResource tempResource = m_GALDeviceDX11.CopyToTempBuffer(sourceData))
      {
        m_GALDeviceDX11.UnmapTempResource(tempResource);

        D3D11_BOX srcBox = {0, 0, 0, sourceData.GetCount(), 1, 1};
        m_pDXContext->CopySubresourceRegion(pDXDestination, 0, uiDestOffset, 0, 0, tempResource.m_pResource, 0, &srcBox);
      }
      else
      {
        EZ_REPORT_FAILURE("Could not find a temp buffer for update.");
      }
    }
    else
    {
      D3D11_MAP mapType = D3D11_MAP_WRITE_NO_OVERWRITE;
      if (!m_AlreadyUpdatedTransientBuffers.Contains(pDestination))
      {
        // If this is the first time we update a transient buffer this frame, we can use DISCARD which will allow us to safely use NO_OVERWRITE on this buffer for this frame afterwards.
        // This is guaranteed by the constraint that the buffer must not be updated twice on the same memory location within one frame.
        m_AlreadyUpdatedTransientBuffers.Insert(pDestination);
        mapType = D3D11_MAP_WRITE_DISCARD;
      }

      D3D11_MAPPED_SUBRESOURCE MapResult;
      if (SUCCEEDED(m_pDXContext->Map(pDXDestination, 0, mapType, 0, &MapResult)))
      {
        memcpy(ezMemoryUtils::AddByteOffset(MapResult.pData, uiDestOffset), sourceData.GetPtr(), sourceData.GetCount());

        m_pDXContext->Unmap(pDXDestination, 0);
      }
      else
      {
        ezLog::Error("Could not map buffer to update content.");
      }
    }
  }
}

void ezGALCommandEncoderImplDX11::CopyTexturePlatform(const ezGALTexture* pDestination, const ezGALTexture* pSource)
{
  ID3D11Resource* pDXDestination = static_cast<const ezGALTextureDX11*>(pDestination)->GetDXTexture();
  ID3D11Resource* pDXSource = static_cast<const ezGALTextureDX11*>(pSource)->GetDXTexture();

  m_pDXContext->CopyResource(pDXDestination, pDXSource);
}

void ezGALCommandEncoderImplDX11::CopyTextureRegionPlatform(const ezGALTexture* pDestination, const ezGALTextureSubresource& destinationSubResource,
  const ezVec3U32& vDestinationPoint, const ezGALTexture* pSource, const ezGALTextureSubresource& sourceSubResource, const ezBoundingBoxu32& box)
{
  ID3D11Resource* pDXDestination = static_cast<const ezGALTextureDX11*>(pDestination)->GetDXTexture();
  ID3D11Resource* pDXSource = static_cast<const ezGALTextureDX11*>(pSource)->GetDXTexture();

  ezUInt32 dstSubResource = D3D11CalcSubresource(
    destinationSubResource.m_uiMipLevel, destinationSubResource.m_uiArraySlice, pDestination->GetDescription().m_uiMipLevelCount);
  ezUInt32 srcSubResource =
    D3D11CalcSubresource(sourceSubResource.m_uiMipLevel, sourceSubResource.m_uiArraySlice, pSource->GetDescription().m_uiMipLevelCount);

  D3D11_BOX srcBox = {box.m_vMin.x, box.m_vMin.y, box.m_vMin.z, box.m_vMax.x, box.m_vMax.y, box.m_vMax.z};
  m_pDXContext->CopySubresourceRegion(
    pDXDestination, dstSubResource, vDestinationPoint.x, vDestinationPoint.y, vDestinationPoint.z, pDXSource, srcSubResource, &srcBox);
}

void ezGALCommandEncoderImplDX11::UpdateTexturePlatform(const ezGALTexture* pDestination, const ezGALTextureSubresource& destinationSubResource,
  const ezBoundingBoxu32& destinationBox, const ezGALSystemMemoryDescription& sourceData)
{
  ID3D11Resource* pDXDestination = static_cast<const ezGALTextureDX11*>(pDestination)->GetDXTexture();

  ezUInt32 uiWidth = ezMath::Max(destinationBox.m_vMax.x - destinationBox.m_vMin.x, 1u);
  ezUInt32 uiHeight = ezMath::Max(destinationBox.m_vMax.y - destinationBox.m_vMin.y, 1u);
  ezUInt32 uiDepth = ezMath::Max(destinationBox.m_vMax.z - destinationBox.m_vMin.z, 1u);
  ezGALResourceFormat::Enum format = pDestination->GetDescription().m_Format;

  if (ezGALDeviceDX11::TempResource tempResource = m_GALDeviceDX11.CopyToTempTexture(sourceData, uiWidth, uiHeight, uiDepth, format))
  {
    m_GALDeviceDX11.UnmapTempResource(tempResource);

    ezUInt32 dstSubResource = D3D11CalcSubresource(destinationSubResource.m_uiMipLevel, destinationSubResource.m_uiArraySlice, pDestination->GetDescription().m_uiMipLevelCount);

    D3D11_BOX srcBox = {0, 0, 0, uiWidth, uiHeight, uiDepth};
    m_pDXContext->CopySubresourceRegion(pDXDestination, dstSubResource, destinationBox.m_vMin.x, destinationBox.m_vMin.y, destinationBox.m_vMin.z, tempResource.m_pResource, 0, &srcBox);
  }
  else
  {
    EZ_REPORT_FAILURE("Could not find a temp texture for update.");
  }
}

void ezGALCommandEncoderImplDX11::ResolveTexturePlatform(const ezGALTexture* pDestination, const ezGALTextureSubresource& destinationSubResource,
  const ezGALTexture* pSource, const ezGALTextureSubresource& sourceSubResource)
{
  ID3D11Resource* pDXDestination = static_cast<const ezGALTextureDX11*>(pDestination)->GetDXTexture();
  ID3D11Resource* pDXSource = static_cast<const ezGALTextureDX11*>(pSource)->GetDXTexture();

  ezUInt32 dstSubResource = D3D11CalcSubresource(destinationSubResource.m_uiMipLevel, destinationSubResource.m_uiArraySlice, pDestination->GetDescription().m_uiMipLevelCount);
  ezUInt32 srcSubResource = D3D11CalcSubresource(sourceSubResource.m_uiMipLevel, sourceSubResource.m_uiArraySlice, pSource->GetDescription().m_uiMipLevelCount);

  DXGI_FORMAT DXFormat = m_GALDeviceDX11.GetFormatLookupTable().GetFormatInfo(pDestination->GetDescription().m_Format).m_eResourceViewType;

  m_pDXContext->ResolveSubresource(pDXDestination, dstSubResource, pDXSource, srcSubResource, DXFormat);
}

void ezGALCommandEncoderImplDX11::ReadbackTexturePlatform(const ezGALReadbackTexture* pDestination, const ezGALTexture* pSource)
{
  const ezGALReadbackTextureDX11* pDXDestination = static_cast<const ezGALReadbackTextureDX11*>(pDestination);
  const ezGALTextureDX11* pDXTexture = static_cast<const ezGALTextureDX11*>(pSource);

  // MSAA textures (e.g. backbuffers) need to be converted to non MSAA versions
  const bool bMSAASourceTexture = pDXTexture->GetDescription().m_SampleCount != ezGALMSAASampleCount::None;
  EZ_IGNORE_UNUSED(bMSAASourceTexture);
  EZ_ASSERT_DEV(!bMSAASourceTexture, "MSAA readback is not supported");
  m_pDXContext->CopyResource(pDXDestination->GetDXTexture(), pDXTexture->GetDXTexture());
}


void ezGALCommandEncoderImplDX11::ReadbackBufferPlatform(const ezGALReadbackBuffer* pDestination, const ezGALBuffer* pSource)
{
  const ezGALReadbackBufferDX11* pDXDestination = static_cast<const ezGALReadbackBufferDX11*>(pDestination);
  const ezGALBufferDX11* pDXBuffer = static_cast<const ezGALBufferDX11*>(pSource);
  m_pDXContext->CopyResource(pDXDestination->GetDXBuffer(), pDXBuffer->GetDXBuffer());
}

ezUInt32 GetMipSize(ezUInt32 uiSize, ezUInt32 uiMipLevel)
{
  for (ezUInt32 i = 0; i < uiMipLevel; i++)
  {
    uiSize = uiSize / 2;
  }
  return ezMath::Max(1u, uiSize);
}

void ezGALCommandEncoderImplDX11::GenerateMipMapsPlatform(const ezGALTexture* pTexture, ezGALTextureRange range)
{
  const ezGALTextureDX11* pDXResourceView = static_cast<const ezGALTextureDX11*>(pTexture);
  ID3D11ShaderResourceView* pSRV = pDXResourceView->GetSRV(range, pTexture->GetDescription().m_Format);
  if (!pSRV)
  {
    ezLog::Error("Failed to generate DX11 texture SRV for GenerateMipMapsPlatform");
    return;
  }

  m_pDXContext->GenerateMips(pSRV);
}

void ezGALCommandEncoderImplDX11::FlushPlatform()
{
  FlushDeferredStateChanges().IgnoreResult();
  m_GALDeviceDX11.GetFenceQueue().SubmitCurrentFence();
  m_pDXContext->Flush();
}

// Debug helper functions

void ezGALCommandEncoderImplDX11::PushMarkerPlatform(const char* szMarker)
{
  if (m_pDXAnnotation != nullptr)
  {
    ezStringWChar wsMarker(szMarker);
    m_pDXAnnotation->BeginEvent(wsMarker.GetData());
  }
}

void ezGALCommandEncoderImplDX11::PopMarkerPlatform()
{
  if (m_pDXAnnotation != nullptr)
  {
    m_pDXAnnotation->EndEvent();
  }
}

void ezGALCommandEncoderImplDX11::InsertEventMarkerPlatform(const char* szMarker)
{
  if (m_pDXAnnotation != nullptr)
  {
    ezStringWChar wsMarker(szMarker);
    m_pDXAnnotation->SetMarker(wsMarker.GetData());
  }
}

//////////////////////////////////////////////////////////////////////////

void ezGALCommandEncoderImplDX11::BeginRenderingPlatform(const ezGALRenderingSetup& renderingSetup)
{
  if (m_RenderTargetSetup != renderingSetup)
  {
    m_RenderTargetSetup = renderingSetup;

    const ezGALRenderTargetView* pRenderTargetViews[EZ_GAL_MAX_RENDERTARGET_COUNT] = {nullptr};
    const ezGALRenderTargetView* pDepthStencilView = nullptr;

    const ezUInt32 uiRenderTargetCount = m_RenderTargetSetup.GetColorTargetCount();

    bool bFlushNeeded = false;

    for (ezUInt8 uiIndex = 0; uiIndex < uiRenderTargetCount; ++uiIndex)
    {
      const ezGALRenderTargetView* pRenderTargetView = m_GALDeviceDX11.GetRenderTargetView(m_RenderTargetSetup.GetFrameBuffer().m_hColorTarget[uiIndex]);
      if (pRenderTargetView != nullptr)
      {
        const ezGALResourceBase* pTexture = pRenderTargetView->GetTexture()->GetParentResource();

        bFlushNeeded |= UnsetResourceViews(pTexture);
        bFlushNeeded |= UnsetUnorderedAccessViews(pTexture);
      }

      pRenderTargetViews[uiIndex] = pRenderTargetView;
    }

    if (m_RenderTargetSetup.HasDepthStencilTarget())
    {
      pDepthStencilView = m_GALDeviceDX11.GetRenderTargetView(m_RenderTargetSetup.GetFrameBuffer().m_hDepthTarget);
      if (pDepthStencilView != nullptr)
      {
        const ezGALResourceBase* pTexture = pDepthStencilView->GetTexture()->GetParentResource();

        bFlushNeeded |= UnsetResourceViews(pTexture);
        bFlushNeeded |= UnsetUnorderedAccessViews(pTexture);
      }
    }

    if (bFlushNeeded)
    {
      FlushDeferredStateChanges().IgnoreResult();
    }

    for (ezUInt32 i = 0; i < EZ_GAL_MAX_RENDERTARGET_COUNT; i++)
    {
      m_pBoundRenderTargets[i] = nullptr;
    }
    m_pBoundDepthStencilTarget = nullptr;

    if (uiRenderTargetCount != 0 || pDepthStencilView != nullptr)
    {
      for (ezUInt32 i = 0; i < uiRenderTargetCount; i++)
      {
        if (pRenderTargetViews[i] != nullptr)
        {
          m_pBoundRenderTargets[i] = static_cast<const ezGALRenderTargetViewDX11*>(pRenderTargetViews[i])->GetRenderTargetView();
        }
      }

      if (pDepthStencilView != nullptr)
      {
        m_pBoundDepthStencilTarget = static_cast<const ezGALRenderTargetViewDX11*>(pDepthStencilView)->GetDepthStencilView();
      }

      // Bind rendertargets, bind max(new rt count, old rt count) to overwrite bound rts if new count < old count
      m_pDXContext->OMSetRenderTargets(ezMath::Max(uiRenderTargetCount, m_uiBoundRenderTargetCount), m_pBoundRenderTargets, m_pBoundDepthStencilTarget);

      m_uiBoundRenderTargetCount = uiRenderTargetCount;
    }
    else
    {
      m_pBoundDepthStencilTarget = nullptr;
      m_pDXContext->OMSetRenderTargets(0, nullptr, nullptr);
      m_uiBoundRenderTargetCount = 0;
    }
  }

  for (ezUInt32 i = 0; i < m_uiBoundRenderTargetCount; i++)
  {
    if (m_RenderTargetSetup.GetRenderPass().m_ColorLoadOp[i] == ezGALRenderTargetLoadOp::Clear && m_pBoundRenderTargets[i])
    {
      m_pDXContext->ClearRenderTargetView(m_pBoundRenderTargets[i], m_RenderTargetSetup.GetClearColor((ezUInt8)i).GetData());
    }
  }

  bool bClearDepth = m_RenderTargetSetup.GetRenderPass().m_DepthLoadOp == ezGALRenderTargetLoadOp::Clear;
  bool bClearStencil = m_RenderTargetSetup.GetRenderPass().m_StencilLoadOp == ezGALRenderTargetLoadOp::Clear;
  if ((bClearDepth || bClearStencil) && m_pBoundDepthStencilTarget)
  {
    ezUInt32 uiClearFlags = bClearDepth ? D3D11_CLEAR_DEPTH : 0;
    uiClearFlags |= bClearStencil ? D3D11_CLEAR_STENCIL : 0;

    m_pDXContext->ClearDepthStencilView(m_pBoundDepthStencilTarget, uiClearFlags, m_RenderTargetSetup.GetClearDepth(), m_RenderTargetSetup.GetClearStencil());
  }
}

void ezGALCommandEncoderImplDX11::EndRenderingPlatform()
{
}

void ezGALCommandEncoderImplDX11::BeginComputePlatform()
{
  // We need to unbind all render targets as otherwise using them in a compute shader as input will fail:
  // DEVICE_CSSETSHADERRESOURCES_HAZARD: Resource being set to CS shader resource slot 0 is still bound on output!
  m_RenderTargetSetup = ezGALRenderingSetup();
  m_pDXContext->OMSetRenderTargets(0, nullptr, nullptr);
}
void ezGALCommandEncoderImplDX11::EndComputePlatform()
{
}

// Draw functions

void ezGALCommandEncoderImplDX11::ClearPlatform(const ezColor& clearColor, ezUInt32 uiRenderTargetClearMask, bool bClearDepth, bool bClearStencil, float fDepthClear, ezUInt8 uiStencilClear)
{
  for (ezUInt32 i = 0; i < m_uiBoundRenderTargetCount; i++)
  {
    if (uiRenderTargetClearMask & (1u << i) && m_pBoundRenderTargets[i])
    {
      m_pDXContext->ClearRenderTargetView(m_pBoundRenderTargets[i], clearColor.GetData());
    }
  }

  if ((bClearDepth || bClearStencil) && m_pBoundDepthStencilTarget)
  {
    ezUInt32 uiClearFlags = bClearDepth ? D3D11_CLEAR_DEPTH : 0;
    uiClearFlags |= bClearStencil ? D3D11_CLEAR_STENCIL : 0;

    m_pDXContext->ClearDepthStencilView(m_pBoundDepthStencilTarget, uiClearFlags, fDepthClear, uiStencilClear);
  }
}

ezResult ezGALCommandEncoderImplDX11::DrawPlatform(ezUInt32 uiVertexCount, ezUInt32 uiStartVertex)
{
  EZ_SUCCEED_OR_RETURN(FlushDeferredStateChanges());

  m_pDXContext->Draw(uiVertexCount, uiStartVertex);
  return EZ_SUCCESS;
}

ezResult ezGALCommandEncoderImplDX11::DrawIndexedPlatform(ezUInt32 uiIndexCount, ezUInt32 uiStartIndex)
{
  EZ_SUCCEED_OR_RETURN(FlushDeferredStateChanges());

#if EZ_ENABLED(EZ_COMPILE_FOR_DEBUG)
  m_pDXContext->DrawIndexed(uiIndexCount, uiStartIndex, 0);

  // In debug builds, with a debugger attached, the engine will break on D3D errors
  // this can be very annoying when an error happens repeatedly
  // you can disable it at runtime, by using the debugger to set bChangeBreakPolicy to 'true', or dragging the
  // the instruction pointer into the if
  volatile bool bChangeBreakPolicy = false;
  if (bChangeBreakPolicy)
  {
    if (m_GALDeviceDX11.m_pDebug != nullptr)
    {
      ID3D11InfoQueue* pInfoQueue = nullptr;
      if (SUCCEEDED(m_GALDeviceDX11.m_pDebug->QueryInterface(__uuidof(ID3D11InfoQueue), (void**)&pInfoQueue)))
      {
        // modify these, if you want to keep certain things enabled
        static BOOL bBreakOnCorruption = FALSE;
        static BOOL bBreakOnError = FALSE;
        static BOOL bBreakOnWarning = FALSE;

        pInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, bBreakOnCorruption);
        pInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, bBreakOnError);
        pInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, bBreakOnWarning);
      }
    }
  }
#else
  m_pDXContext->DrawIndexed(uiIndexCount, uiStartIndex, 0);
#endif
  return EZ_SUCCESS;
}

ezResult ezGALCommandEncoderImplDX11::DrawIndexedInstancedPlatform(ezUInt32 uiIndexCountPerInstance, ezUInt32 uiInstanceCount, ezUInt32 uiStartIndex)
{
  EZ_SUCCEED_OR_RETURN(FlushDeferredStateChanges());

  m_pDXContext->DrawIndexedInstanced(uiIndexCountPerInstance, uiInstanceCount, uiStartIndex, 0, 0);
  return EZ_SUCCESS;
}

ezResult ezGALCommandEncoderImplDX11::DrawIndexedInstancedIndirectPlatform(const ezGALBuffer* pIndirectArgumentBuffer, ezUInt32 uiArgumentOffsetInBytes)
{
  EZ_SUCCEED_OR_RETURN(FlushDeferredStateChanges());

  m_pDXContext->DrawIndexedInstancedIndirect(static_cast<const ezGALBufferDX11*>(pIndirectArgumentBuffer)->GetDXBuffer(), uiArgumentOffsetInBytes);
  return EZ_SUCCESS;
}

ezResult ezGALCommandEncoderImplDX11::DrawInstancedPlatform(ezUInt32 uiVertexCountPerInstance, ezUInt32 uiInstanceCount, ezUInt32 uiStartVertex)
{
  EZ_SUCCEED_OR_RETURN(FlushDeferredStateChanges());

  m_pDXContext->DrawInstanced(uiVertexCountPerInstance, uiInstanceCount, uiStartVertex, 0);
  return EZ_SUCCESS;
}

ezResult ezGALCommandEncoderImplDX11::DrawInstancedIndirectPlatform(const ezGALBuffer* pIndirectArgumentBuffer, ezUInt32 uiArgumentOffsetInBytes)
{
  EZ_SUCCEED_OR_RETURN(FlushDeferredStateChanges());

  m_pDXContext->DrawInstancedIndirect(static_cast<const ezGALBufferDX11*>(pIndirectArgumentBuffer)->GetDXBuffer(), uiArgumentOffsetInBytes);
  return EZ_SUCCESS;
}

void ezGALCommandEncoderImplDX11::SetIndexBufferPlatform(const ezGALBuffer* pIndexBuffer)
{
  if (pIndexBuffer != nullptr)
  {
    const ezGALBufferDX11* pDX11Buffer = static_cast<const ezGALBufferDX11*>(pIndexBuffer);
    m_pDXContext->IASetIndexBuffer(pDX11Buffer->GetDXBuffer(), pDX11Buffer->GetIndexFormat(), 0 /* \todo: Expose */);
  }
  else
  {
    m_pDXContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0);
  }
}

void ezGALCommandEncoderImplDX11::SetVertexBufferPlatform(ezUInt32 uiSlot, const ezGALBuffer* pVertexBuffer, ezUInt32 uiOffset)
{
  EZ_ASSERT_DEV(uiSlot < EZ_GAL_MAX_VERTEX_BUFFER_COUNT, "Invalid slot index");

  m_pBoundVertexBuffers[uiSlot] = pVertexBuffer != nullptr ? static_cast<const ezGALBufferDX11*>(pVertexBuffer)->GetDXBuffer() : nullptr;
  m_VertexBufferOffsets[uiSlot] = uiOffset;
  m_BoundVertexBuffersRange.SetToIncludeValue(uiSlot);
}

void ezGALCommandEncoderImplDX11::SetGraphicsPipelinePlatform(const ezGALGraphicsPipeline* pGraphicsPipeline)
{
  const ezGALShader* pShader = nullptr;
  const ezGALVertexDeclaration* pVertexDeclaration = nullptr;
  const ezGALRasterizerState* pRasterizerState = nullptr;
  const ezGALBlendState* pBlendState = nullptr;
  const ezGALDepthStencilState* pDepthStencilState = nullptr;

  if (pGraphicsPipeline)
  {
    const ezGALGraphicsPipelineCreationDescription& desc = pGraphicsPipeline->GetDescription();
    pShader = m_GALDeviceDX11.GetShader(desc.m_hShader);
    EZ_ASSERT_DEBUG(pShader->GetDescription().m_ByteCodes[ezGALShaderStage::ComputeShader] == nullptr, "");
    pVertexDeclaration = m_GALDeviceDX11.GetVertexDeclaration(desc.m_hVertexDeclaration);
    pRasterizerState = m_GALDeviceDX11.GetRasterizerState(desc.m_hRasterizerState);
    pBlendState = m_GALDeviceDX11.GetBlendState(desc.m_hBlendState);
    pDepthStencilState = m_GALDeviceDX11.GetDepthStencilState(desc.m_hDepthStencilState);
    SetPrimitiveTopology(desc.m_Topology);
  }

  SetShader(pShader);
  SetVertexDeclaration(pVertexDeclaration);
  SetRasterizerState(pRasterizerState);
  SetBlendState(pBlendState);
  SetDepthStencilState(pDepthStencilState);
}

void ezGALCommandEncoderImplDX11::SetComputePipelinePlatform(const ezGALComputePipeline* pComputePipeline)
{
  const ezGALShader* pShader = nullptr;

  if (pComputePipeline)
  {
    const ezGALComputePipelineCreationDescription& desc = pComputePipeline->GetDescription();
    pShader = m_GALDeviceDX11.GetShader(desc.m_hShader);
    EZ_ASSERT_DEBUG(pShader->GetDescription().m_ByteCodes[ezGALShaderStage::ComputeShader] != nullptr, "");
  }

  SetShader(pShader);
}

void ezGALCommandEncoderImplDX11::SetVertexDeclaration(const ezGALVertexDeclaration* pVertexDeclaration)
{
  ezMemoryUtils::ZeroFill(m_VertexBufferStrides, EZ_ARRAY_SIZE(m_VertexBufferStrides));
  auto pVertexDeclarationDX11 = static_cast<const ezGALVertexDeclarationDX11*>(pVertexDeclaration);
  if (pVertexDeclaration)
  {
    ezArrayPtr<const ezUInt32> strides = pVertexDeclarationDX11->GetVertexBufferStrides();
    if (!strides.IsEmpty())
    {
      m_BoundVertexBuffersRange.SetToIncludeValue(0);
      m_BoundVertexBuffersRange.SetToIncludeValue(strides.GetCount() - 1);
    }
    ezMemoryUtils::Copy(m_VertexBufferStrides, strides.GetPtr(), strides.GetCount());
    m_pDXContext->IASetInputLayout(pVertexDeclarationDX11->GetDXInputLayout());
  }
  else
  {
    m_pDXContext->IASetInputLayout(nullptr);
  }
}

static const D3D11_PRIMITIVE_TOPOLOGY GALTopologyToDX11[] = {
  D3D11_PRIMITIVE_TOPOLOGY_POINTLIST,
  D3D11_PRIMITIVE_TOPOLOGY_LINELIST,
  D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
  D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
};

static_assert(EZ_ARRAY_SIZE(GALTopologyToDX11) == ezGALPrimitiveTopology::ENUM_COUNT);

void ezGALCommandEncoderImplDX11::SetPrimitiveTopology(ezGALPrimitiveTopology::Enum topology)
{
  m_Topology = topology;
}

void ezGALCommandEncoderImplDX11::SetBlendState(const ezGALBlendState* pBlendState, const ezColor& blendFactor, ezUInt32 uiSampleMask)
{
  FLOAT BlendFactors[4] = {blendFactor.r, blendFactor.g, blendFactor.b, blendFactor.a};

  m_pDXContext->OMSetBlendState(
    pBlendState != nullptr ? static_cast<const ezGALBlendStateDX11*>(pBlendState)->GetDXBlendState() : nullptr, BlendFactors, uiSampleMask);
}

void ezGALCommandEncoderImplDX11::SetDepthStencilState(const ezGALDepthStencilState* pDepthStencilState)
{
  ID3D11DepthStencilState* pDepthStencilStateDX11 = pDepthStencilState != nullptr ? static_cast<const ezGALDepthStencilStateDX11*>(pDepthStencilState)->GetDXDepthStencilState() : nullptr;
  m_pDXContext->OMSetDepthStencilState(pDepthStencilStateDX11, m_uiStencilRefValue);
}

void ezGALCommandEncoderImplDX11::SetRasterizerState(const ezGALRasterizerState* pRasterizerState)
{
  m_pDXContext->RSSetState(pRasterizerState != nullptr ? static_cast<const ezGALRasterizerStateDX11*>(pRasterizerState)->GetDXRasterizerState() : nullptr);
}

void ezGALCommandEncoderImplDX11::SetViewportPlatform(const ezRectFloat& rect, float fMinDepth, float fMaxDepth)
{
  D3D11_VIEWPORT Viewport;
  Viewport.TopLeftX = rect.x;
  Viewport.TopLeftY = rect.y;
  Viewport.Width = rect.width;
  Viewport.Height = rect.height;
  Viewport.MinDepth = fMinDepth;
  Viewport.MaxDepth = fMaxDepth;

  m_pDXContext->RSSetViewports(1, &Viewport);
}

void ezGALCommandEncoderImplDX11::SetScissorRectPlatform(const ezRectU32& rect)
{
  D3D11_RECT ScissorRect;
  ScissorRect.left = rect.x;
  ScissorRect.top = rect.y;
  ScissorRect.right = rect.x + rect.width;
  ScissorRect.bottom = rect.y + rect.height;

  m_pDXContext->RSSetScissorRects(1, &ScissorRect);
}

void ezGALCommandEncoderImplDX11::SetStencilReferencePlatform(ezUInt8 uiStencilRefValue)
{
  if (m_uiStencilRefValue == uiStencilRefValue)
    return;

  m_uiStencilRefValue = uiStencilRefValue;
  ID3D11DepthStencilState* pState = nullptr;
  m_pDXContext->OMGetDepthStencilState(&pState, nullptr);
  m_pDXContext->OMSetDepthStencilState(pState, m_uiStencilRefValue);
}

//////////////////////////////////////////////////////////////////////////

ezResult ezGALCommandEncoderImplDX11::DispatchPlatform(ezUInt32 uiThreadGroupCountX, ezUInt32 uiThreadGroupCountY, ezUInt32 uiThreadGroupCountZ)
{
  EZ_SUCCEED_OR_RETURN(FlushDeferredStateChanges());

  m_pDXContext->Dispatch(uiThreadGroupCountX, uiThreadGroupCountY, uiThreadGroupCountZ);
  return EZ_SUCCESS;
}

ezResult ezGALCommandEncoderImplDX11::DispatchIndirectPlatform(const ezGALBuffer* pIndirectArgumentBuffer, ezUInt32 uiArgumentOffsetInBytes)
{
  EZ_SUCCEED_OR_RETURN(FlushDeferredStateChanges());

  m_pDXContext->DispatchIndirect(static_cast<const ezGALBufferDX11*>(pIndirectArgumentBuffer)->GetDXBuffer(), uiArgumentOffsetInBytes);
  return EZ_SUCCESS;
}

//////////////////////////////////////////////////////////////////////////

static void SetShaderResources(ezGALShaderStage::Enum stage, ID3D11DeviceContext* pContext, ezUInt32 uiStartSlot, ezUInt32 uiNumSlots,
  ID3D11ShaderResourceView** pShaderResourceViews)
{
  switch (stage)
  {
    case ezGALShaderStage::VertexShader:
      pContext->VSSetShaderResources(uiStartSlot, uiNumSlots, pShaderResourceViews);
      break;
    case ezGALShaderStage::HullShader:
      pContext->HSSetShaderResources(uiStartSlot, uiNumSlots, pShaderResourceViews);
      break;
    case ezGALShaderStage::DomainShader:
      pContext->DSSetShaderResources(uiStartSlot, uiNumSlots, pShaderResourceViews);
      break;
    case ezGALShaderStage::GeometryShader:
      pContext->GSSetShaderResources(uiStartSlot, uiNumSlots, pShaderResourceViews);
      break;
    case ezGALShaderStage::PixelShader:
      pContext->PSSetShaderResources(uiStartSlot, uiNumSlots, pShaderResourceViews);
      break;
    case ezGALShaderStage::ComputeShader:
      pContext->CSSetShaderResources(uiStartSlot, uiNumSlots, pShaderResourceViews);
      break;
    default:
      EZ_ASSERT_NOT_IMPLEMENTED;
  }
}

static void SetConstantBuffers(
  ezGALShaderStage::Enum stage, ID3D11DeviceContext* pContext, ezUInt32 uiStartSlot, ezUInt32 uiNumSlots, ID3D11Buffer** pConstantBuffers)
{
  switch (stage)
  {
    case ezGALShaderStage::VertexShader:
      pContext->VSSetConstantBuffers(uiStartSlot, uiNumSlots, pConstantBuffers);
      break;
    case ezGALShaderStage::HullShader:
      pContext->HSSetConstantBuffers(uiStartSlot, uiNumSlots, pConstantBuffers);
      break;
    case ezGALShaderStage::DomainShader:
      pContext->DSSetConstantBuffers(uiStartSlot, uiNumSlots, pConstantBuffers);
      break;
    case ezGALShaderStage::GeometryShader:
      pContext->GSSetConstantBuffers(uiStartSlot, uiNumSlots, pConstantBuffers);
      break;
    case ezGALShaderStage::PixelShader:
      pContext->PSSetConstantBuffers(uiStartSlot, uiNumSlots, pConstantBuffers);
      break;
    case ezGALShaderStage::ComputeShader:
      pContext->CSSetConstantBuffers(uiStartSlot, uiNumSlots, pConstantBuffers);
      break;
    default:
      EZ_ASSERT_NOT_IMPLEMENTED;
  }
}

static void SetSamplers(
  ezGALShaderStage::Enum stage, ID3D11DeviceContext* pContext, ezUInt32 uiStartSlot, ezUInt32 uiNumSlots, ID3D11SamplerState** pSamplerStates)
{
  switch (stage)
  {
    case ezGALShaderStage::VertexShader:
      pContext->VSSetSamplers(uiStartSlot, uiNumSlots, pSamplerStates);
      break;
    case ezGALShaderStage::HullShader:
      pContext->HSSetSamplers(uiStartSlot, uiNumSlots, pSamplerStates);
      break;
    case ezGALShaderStage::DomainShader:
      pContext->DSSetSamplers(uiStartSlot, uiNumSlots, pSamplerStates);
      break;
    case ezGALShaderStage::GeometryShader:
      pContext->GSSetSamplers(uiStartSlot, uiNumSlots, pSamplerStates);
      break;
    case ezGALShaderStage::PixelShader:
      pContext->PSSetSamplers(uiStartSlot, uiNumSlots, pSamplerStates);
      break;
    case ezGALShaderStage::ComputeShader:
      pContext->CSSetSamplers(uiStartSlot, uiNumSlots, pSamplerStates);
      break;
    default:
      EZ_ASSERT_NOT_IMPLEMENTED;
  }
}

// Some state changes are deferred so they can be updated faster
ezResult ezGALCommandEncoderImplDX11::FlushDeferredStateChanges()
{
  if (m_uiTessellationPatchControlPoints == 0)
  {
    m_pDXContext->IASetPrimitiveTopology(GALTopologyToDX11[m_Topology.GetValue()]);
  }
  else
  {
    m_pDXContext->IASetPrimitiveTopology(static_cast<D3D_PRIMITIVE_TOPOLOGY>(D3D11_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + (m_uiTessellationPatchControlPoints - 1)));
  }

  if (m_BoundVertexBuffersRange.IsValid())
  {
    const ezUInt32 uiStartSlot = m_BoundVertexBuffersRange.m_uiMin;
    const ezUInt32 uiNumSlots = m_BoundVertexBuffersRange.GetCount();

    m_pDXContext->IASetVertexBuffers(uiStartSlot, uiNumSlots, m_pBoundVertexBuffers + uiStartSlot, m_VertexBufferStrides + uiStartSlot, m_VertexBufferOffsets + uiStartSlot);

    m_BoundVertexBuffersRange.Reset();
  }

  for (ezUInt32 stage = 0; stage < ezGALShaderStage::ENUM_COUNT; ++stage)
  {
    if (m_pBoundShaders[stage] != nullptr && m_BoundConstantBuffersRange[stage].IsValid())
    {
      const ezUInt32 uiStartSlot = m_BoundConstantBuffersRange[stage].m_uiMin;
      const ezUInt32 uiNumSlots = m_BoundConstantBuffersRange[stage].GetCount();

      SetConstantBuffers((ezGALShaderStage::Enum)stage, m_pDXContext, uiStartSlot, uiNumSlots, m_pBoundConstantBuffers + uiStartSlot);

      m_BoundConstantBuffersRange[stage].Reset();
    }
  }

  // Do UAV bindings before SRV since UAV are outputs which need to be unbound before they are potentially rebound as SRV again.
  if (m_BoundUnorderedAccessViewsRange.IsValid())
  {
    const ezUInt32 uiStartSlot = m_BoundUnorderedAccessViewsRange.m_uiMin;
    const ezUInt32 uiNumSlots = m_BoundUnorderedAccessViewsRange.GetCount();
    m_pDXContext->CSSetUnorderedAccessViews(uiStartSlot, uiNumSlots, m_BoundUnorderedAccessViews.GetData() + uiStartSlot, nullptr); // Todo: Count reset.

    m_BoundUnorderedAccessViewsRange.Reset();
  }

  for (ezUInt32 stage = 0; stage < ezGALShaderStage::ENUM_COUNT; ++stage)
  {
    // Need to do bindings even on inactive shader stages since we might miss unbindings otherwise!
    if (m_BoundShaderResourceViewsRange[stage].IsValid())
    {
      const ezUInt32 uiStartSlot = m_BoundShaderResourceViewsRange[stage].m_uiMin;
      const ezUInt32 uiNumSlots = m_BoundShaderResourceViewsRange[stage].GetCount();

      SetShaderResources((ezGALShaderStage::Enum)stage, m_pDXContext, uiStartSlot, uiNumSlots, m_pBoundShaderResourceViews[stage].GetData() + uiStartSlot);

      m_BoundShaderResourceViewsRange[stage].Reset();
    }

    // Don't need to unset sampler stages for unbound shader stages.
    if (m_pBoundShaders[stage] == nullptr)
      continue;

    if (m_BoundSamplerStatesRange[stage].IsValid())
    {
      const ezUInt32 uiStartSlot = m_BoundSamplerStatesRange[stage].m_uiMin;
      const ezUInt32 uiNumSlots = m_BoundSamplerStatesRange[stage].GetCount();

      SetSamplers((ezGALShaderStage::Enum)stage, m_pDXContext, uiStartSlot, uiNumSlots, m_pBoundSamplerStates[stage] + uiStartSlot);

      m_BoundSamplerStatesRange[stage].Reset();
    }
  }
  return EZ_SUCCESS;
}

bool ezGALCommandEncoderImplDX11::UnsetUnorderedAccessViews(const ezGALResourceBase* pResource)
{
  EZ_ASSERT_DEV(pResource->GetParentResource() == pResource, "No proxies allowed");

  bool bResult = false;

  for (ezUInt32 uiSlot = 0; uiSlot < m_ResourcesForUnorderedAccessViews.GetCount(); ++uiSlot)
  {
    if (m_ResourcesForUnorderedAccessViews[uiSlot] == pResource)
    {
      m_ResourcesForUnorderedAccessViews[uiSlot] = nullptr;
      m_BoundUnorderedAccessViews[uiSlot] = nullptr;
      m_BoundUnorderedAccessViewsRange.SetToIncludeValue(uiSlot);
      bResult = true;
    }
  }

  return bResult;
}
bool ezGALCommandEncoderImplDX11::UnsetResourceViews(const ezGALResourceBase* pResource)
{
  EZ_ASSERT_DEV(pResource->GetParentResource() == pResource, "No proxies allowed");

  bool bResult = false;

  for (ezUInt32 stage = 0; stage < ezGALShaderStage::ENUM_COUNT; ++stage)
  {
    for (ezUInt32 uiSlot = 0; uiSlot < m_ResourcesForResourceViews[stage].GetCount(); ++uiSlot)
    {
      if (m_ResourcesForResourceViews[stage][uiSlot] == pResource)
      {
        m_ResourcesForResourceViews[stage][uiSlot] = nullptr;
        m_pBoundShaderResourceViews[stage][uiSlot] = nullptr;
        m_BoundShaderResourceViewsRange[stage].SetToIncludeValue(uiSlot);
        bResult = true;
      }
    }
  }

  return bResult;
}
